home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7684 / 7684.xpi / resources / fmPlayer.js < prev    next >
Encoding:
JavaScript  |  2009-11-20  |  12.6 KB  |  457 lines

  1. /**
  2.  * Copyright (c) 2008, Jose Enrique Bolanos, Jorge Villalobos
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions are met:
  7.  *
  8.  *  * Redistributions of source code must retain the above copyright notice,
  9.  *    this list of conditions and the following disclaimer.
  10.  *  * Redistributions in binary form must reproduce the above copyright notice,
  11.  *    this list of conditions and the following disclaimer in the documentation
  12.  *    and/or other materials provided with the distribution.
  13.  *  * Neither the name of Jose Enrique Bolanos, Jorge Villalobos nor the names
  14.  *    of its contributors may be used to endorse or promote products derived
  15.  *    from this software without specific prior written permission.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  21.  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  22.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  23.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  24.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  25.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28.  **/
  29.  
  30. var EXPORTED_SYMBOLS = [ "FireFM.Player" ];
  31.  
  32. const Cc = Components.classes;
  33. const Ci = Components.interfaces;
  34. const Ce = Components.Exception;
  35.  
  36. Components.utils.import("resource://firefm/fmCommon.js");
  37. Components.utils.import("resource://firefm/fmPlaylist.js");
  38.  
  39. // Timeout delay in ms for the progress check timer
  40. const PROGRESS_TIMEOUT = 100;
  41. // Number of progress ticks before declaring "buffering state"
  42. const BUFFERING_TICKS_LIMIT = 20; // 2 seconds if progress timeout is 100ms
  43. // Maximum number of consecutive load failures to accept
  44. const MAX_LOAD_FAILURES = 3;
  45.  
  46. /**
  47.  * FireFM Player. Controls playback of music tracks.
  48.  */
  49. if (typeof(FireFM.Player) == 'undefined') {
  50. FireFM.Player = {
  51.  
  52.   // Topic notifications sent from this object
  53.   get TOPIC_PLAYER_READY()     { return "firefm-player-ready";     },
  54.   get TOPIC_TRACK_LOADED()     { return "firefm-track-loaded";     },
  55.   get TOPIC_PLAYER_STOPPED()   { return "firefm-player-stopped";   },
  56.   get TOPIC_PROGRESS_CHANGED() { return "firefm-progress-changed"; },
  57.   get TOPIC_PLAYER_ERROR()     { return "firefm-player-error";     },
  58.  
  59.   // Constants for the possible player initialization statuses
  60.   get STATUS_READY()          { return 0; },
  61.   get STATUS_PLUGIN_MISSING() { return 1; },
  62.   get STATUS_LOAD_ERROR()     { return 2; },
  63.   get STATUS_LOAD_FAILED()   { return 3; },
  64.  
  65.   /* Logger for this object. */
  66.   _logger : null,
  67.   /* Reference to the embedded player object. */
  68.   _playerObj : null,
  69.   /* Timer used to check track playback progression. */
  70.   _timer : null,
  71.   /* Holds the info of the track being played. */
  72.   _track : null,
  73.   /* Holds the value of the status property. */
  74.   _statusValue : null,
  75.   /* Holds the value of the isPlaying property. */
  76.   _isPlayingValue : false,
  77.   /* Holds the value of the isBuffering property. */
  78.   _isBufferingValue : false,
  79.   /* Holds the value of the elapsed time the last the progress tick method
  80.      was fired. */
  81.   _previousElapsedTime : 0,
  82.   /* Counts the number of ticks the player has been in buffering state. */
  83.   _bufferingTicks : 0,
  84.   /* Holds the volume value that was set before loading a track. */
  85.   _volumeBeforeLoad : null,
  86.   /* Counts the number of consecutive track load failures. */
  87.   _loadFailureCount : 0,
  88.  
  89.   /**
  90.    * Initializes the object.
  91.    */
  92.   init : function() {
  93.     this._logger = FireFM.getLogger("FireFM.Player", "Info");
  94.     this._logger.debug("init");
  95.  
  96.     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  97.   },
  98.  
  99.   /**
  100.    * Sets the values of the player (flash player object, status).
  101.    * @param aFlashObject Reference to the loaded flash player object. Null if
  102.    * the player was not loaded correctly.
  103.    * @param aStatus The flash initialization status.
  104.    */
  105.   initializePlayer : function(aFlashObject, aStatus) {
  106.     this._logger.info("initializePlayer");
  107.  
  108.     this._playerObj = aFlashObject;
  109.     this._statusValue = aStatus;
  110.  
  111.     if (this.STATUS_READY == this._statusValue) {
  112.       FireFM.obsService.notifyObservers(null, this.TOPIC_PLAYER_READY, null);
  113.     }
  114.   },
  115.  
  116.   /**
  117.    * Removes all player references when the application is shutting down.
  118.    */
  119.   uninitializePlayer : function() {
  120.     this._logger.info("uninitializePlayer");
  121.     this._playerObj.stop();
  122.     this._playerObj = null;
  123.   },
  124.  
  125.   /**
  126.    * Event fired by the flash object when a track has finished loading.
  127.    * @param aSuccess Whether the track was loaded successfully.
  128.    */
  129.   onTrackLoaded : function(aSuccess) {
  130.     this._logger.debug("onTrackLoaded: Success=" + aSuccess);
  131.  
  132.     if (!aSuccess) {
  133.       this._loadFailureCount++;
  134.  
  135.       if (MAX_LOAD_FAILURES <= this._loadFailureCount) {
  136.         FireFM.Station.stop();
  137.         this._statusValue = this.STATUS_LOAD_FAILED;
  138.         FireFM.obsService.
  139.           notifyObservers(null, this.TOPIC_PLAYER_ERROR, null);
  140.         this._statusValue = this.STATUS_READY;
  141.         this.stop();
  142.       } else {
  143.         this.skip();
  144.       }
  145.     }
  146.   },
  147.  
  148.   /**
  149.    * Event fired by the flash object when a track has finished playing.
  150.    */
  151.   onTrackFinished : function() {
  152.     this._logger.debug("onTrackFinished");
  153.     this.skip();
  154.   },
  155.  
  156.   /**
  157.    * Loads a track in the player and starts playing it.
  158.    * @param aURL The URL of the track to be loaded.
  159.    */
  160.   _loadTrack : function(aURL) {
  161.     this._logger.trace("_loadTrack");
  162.  
  163.     if (null == this._volumeBeforeLoad) {
  164.       this._volumeBeforeLoad = this.volume;
  165.     }
  166.  
  167.     this._playerObj.stop();
  168.     this._playerObj.loadSound(aURL, true);
  169.   },
  170.  
  171.   /**
  172.    * Begins playlist playback.
  173.    */
  174.   play : function() {
  175.     this._logger.debug("play");
  176.  
  177.     let nextTrack = FireFM.Playlist.getNextTrack();
  178.  
  179.     if (null != nextTrack) {
  180.       this._track = nextTrack;
  181.  
  182.       this._loadTrack(nextTrack.location);
  183.       this._isPlaying = true;
  184.       nextTrack.startTime = Math.floor((new Date()).getTime() / 1000);
  185.  
  186.       FireFM.obsService.notifyObservers(
  187.         nextTrack, this.TOPIC_TRACK_LOADED, null);
  188.     } else {
  189.       // try to load a new playlist.
  190.       FireFM.Station.play();
  191.     }
  192.   },
  193.  
  194.   /**
  195.    * Skips the current track.
  196.    */
  197.   skip : function() {
  198.     this._logger.debug("skip");
  199.  
  200.     this._bufferingTicks = 0;
  201.     this._isBufferingValue = false;
  202.     this._isPlaying = false;
  203.     this.play();
  204.   },
  205.  
  206.   /**
  207.    * Stops track playback.
  208.    */
  209.   stop : function() {
  210.     this._logger.debug("stop");
  211.  
  212.     this._bufferingTicks = 0;
  213.     this._isBufferingValue = false;
  214.     this._isPlaying = false;
  215.     this._playerObj.stop();
  216.  
  217.     FireFM.obsService.notifyObservers(null, this.TOPIC_PLAYER_STOPPED, null);
  218.   },
  219.  
  220.   /**
  221.    * Getter of the track that is currently loaded.
  222.    * @return The track object.
  223.    */
  224.   get track() {
  225.     this._logger.debug("track[get]");
  226.     return this._track;
  227.   },
  228.  
  229.   /**
  230.    * Getter of the current player volume value.
  231.    * @return The volume value (0 to 100).
  232.    */
  233.   get volume() {
  234.     this._logger.debug("volume[get]");
  235.  
  236.     let volume;
  237.     if (null != this._volumeBeforeLoad) {
  238.       volume = this._volumeBeforeLoad;
  239.     } else {
  240.       volume = this._playerObj.getVolume();
  241.     }
  242.  
  243.     return volume;
  244.   },
  245.  
  246.   /**
  247.    * Sets the volume of the player if the given volume value is in the range of
  248.    * 0 and 100.
  249.    * @param aVolumeValue The new volume value to be set.
  250.    */
  251.   setVolume : function(aVolumeValue) {
  252.     this._logger.debug("setVolume");
  253.  
  254.     if (0 <= aVolumeValue && aVolumeValue <= 100) {
  255.       if (this.isPlaying) {
  256.         this._playerObj.setVolume(aVolumeValue);
  257.       } else {
  258.         this._volumeBeforeLoad = aVolumeValue;
  259.       }
  260.     }
  261.   },
  262.  
  263.   /**
  264.    * Getter of the track duration.
  265.    * @return The track duration string.
  266.    */
  267.   get trackDuration() {
  268.     this._logger.debug("trackDuration[get]");
  269.  
  270.     return this._toHumanTime(this.rawTrackDuration);
  271.   },
  272.  
  273.   /**
  274.    * Getter of the track elapsed time.
  275.    * @return The elapsed time string.
  276.    */
  277.   get elapsedTime() {
  278.     this._logger.debug("elapsedTime[get]");
  279.  
  280.     return this._toHumanTime(this.rawElapsedTime);
  281.   },
  282.  
  283.   /**
  284.    * Getter of the track remaining time.
  285.    * @return The remaining time string.
  286.    */
  287.   get remainingTime() {
  288.     this._logger.debug("remainingTime[get]");
  289.  
  290.     return this._toHumanTime(this.rawTrackDuration - this.rawElapsedTime);
  291.   },
  292.  
  293.   /**
  294.    * Getter of the raw track elapsed time.
  295.    * @return The raw track elapsed time in miliseconds.
  296.    */
  297.   get rawElapsedTime() {
  298.     this._logger.debug("rawElapsedTime[get]");
  299.  
  300.     // XXX: Elapsed set to zero if stopped because we don't allow pause, and
  301.     // the flash player does not reset its position.
  302.     let  elapsed = this.isPlaying ? this._playerObj.getPosition() : 0;
  303.     if (isNaN(elapsed)) {
  304.       elapsed = 0;
  305.     }
  306.  
  307.     return elapsed;
  308.   },
  309.  
  310.   /**
  311.    * Getter of the raw track duration time.
  312.    * @return The raw track duration in miliseconds.
  313.    */
  314.   get rawTrackDuration() {
  315.     this._logger.debug("rawTrackDuration[get]");
  316.     return (null != this._track ? this._track.duration : 0);
  317.   },
  318.  
  319.   /**
  320.    * Getter of the number of bytes loaded.
  321.    * @return The number of bytes loaded so far.
  322.    */
  323.   get bytesLoaded() {
  324.     this._logger.debug("bytesLoaded[get]");
  325.     return this._playerObj.getBytesLoaded();
  326.   },
  327.  
  328.   /**
  329.    * Getter of the total number of bytes of the loaded track.
  330.    * @return The total number of bytes.
  331.    */
  332.   get bytesTotal() {
  333.     this._logger.debug("bytesTotal[get]");
  334.     return this._playerObj.getBytesTotal();
  335.   },
  336.  
  337.   /**
  338.    * Obtains the current status of the player.
  339.    * @return The current status of the player.
  340.    */
  341.   get status() {
  342.     this._logger.debug("status[get]");
  343.     return this._statusValue;
  344.   },
  345.  
  346.   /**
  347.    * Whether the player is currently playing something.
  348.    * @return True if playing, false if stopped.
  349.    */
  350.   get isPlaying() {
  351.     this._logger.debug("isPlaying[get]");
  352.     return this._isPlayingValue;
  353.   },
  354.  
  355.   /**
  356.    * Whether the player is buffering a track.
  357.    * @return True if buffering, false otherwise.
  358.    */
  359.   get isBuffering() {
  360.     this._logger.debug("isBuffering[get]");
  361.     return this._isBufferingValue;
  362.   },
  363.  
  364.   /**
  365.    * Sets the value for the isPlaying property. It starts and stops the
  366.    * progress check timer.
  367.    */
  368.   set _isPlaying(aValue) {
  369.     this._logger.debug("_isPlaying[set]");
  370.  
  371.     if (this._isPlayingValue != aValue) {
  372.  
  373.       this._isPlayingValue = aValue;
  374.       this._timer.cancel();
  375.  
  376.       if (this._isPlayingValue) {
  377.  
  378.         this._timer.initWithCallback(
  379.           { notify: function(aTimer) { FireFM.Player._progressTick(); } },
  380.           PROGRESS_TIMEOUT, Ci.nsITimer.TYPE_REPEATING_SLACK);
  381.  
  382.       } else {
  383.         this._progressTick();
  384.       }
  385.     }
  386.   },
  387.  
  388.   /**
  389.    * Called every time the progress timer ticks. Calculates the isBuffering
  390.    * property and notifies observers of the progress changed topic.
  391.    */
  392.   _progressTick : function() {
  393.     // XXX: There is no logging here for efficiency reasons.
  394.  
  395.     let elapsed = this.rawElapsedTime;
  396.     let duration = this.rawTrackDuration;
  397.  
  398.     // Restore the volume value that the player had before loading the track
  399.     if (null != this._volumeBeforeLoad) {
  400.       this.setVolume(this._volumeBeforeLoad);
  401.       this._volumeBeforeLoad = null;
  402.     }
  403.  
  404.     let per = (elapsed  * 100) / duration;
  405.  
  406.     // Calculate isBuffering
  407.     if (this.isPlaying && elapsed == this._previousElapsedTime) {
  408.  
  409.       this._bufferingTicks++;
  410.       this._isBufferingValue =
  411.         (BUFFERING_TICKS_LIMIT <= this._bufferingTicks);
  412.  
  413.     } else {
  414.       this._bufferingTicks = 0;
  415.       this._isBufferingValue = false;
  416.       this._previousElapsedTime = elapsed;
  417.     }
  418.  
  419.     if (elapsed > 0) {
  420.       this._loadFailureCount = 0;
  421.     }
  422.  
  423.     FireFM.obsService.notifyObservers(null, this.TOPIC_PROGRESS_CHANGED, per);
  424.   },
  425.  
  426.   /**
  427.    * Converts a raw track time to a time string in the form of MM:SS, where M
  428.    * is minute and S is second. A zero is added as a prefix to seconds less
  429.    * than ten.
  430.    * @param aTime The raw track time to be converted.
  431.    * @returns The time string.
  432.    */
  433.   _toHumanTime : function(aTime) {
  434.     this._logger.trace("_toHumanTime");
  435.  
  436.     if (0 > aTime) {
  437.       aTime = 0;
  438.     }
  439.  
  440.     var date = new Date(aTime);
  441.  
  442.     var sec = date.getSeconds();
  443.     if (sec < 10) {
  444.       sec = "0" + sec;
  445.     }
  446.  
  447.     return (date.getMinutes() + ":" + sec);
  448.   }
  449. };}
  450.  
  451. /**
  452.  * FireFM.Player constructor.
  453.  */
  454. (function() {
  455.   this.init();
  456. }).apply(FireFM.Player);
  457.